import { ReactNode, useCallback, useEffect, useRef, useState } from 'react';
import './index.less';
import copy from 'copy-to-clipboard';
import { Divider, Space } from 'antd';
import Icon from '@ant-design/icons';
import sh from '@/utils/shiki-highlighter';
import { Highlighter, type Lang } from 'shiki';
import CopySvg from '@/assets/image/copy.svg?react';
import CopiedSvg from '@/assets/image/copied.svg?react';
import clsx from 'clsx';
import { LoadingFallback } from '../LoadingFallback';
import { useTranslation } from 'react-i18next';
import { deployPath, deployUrl } from '@/utils/constants';

const enableSSL = String(window.location.protocol === 'https:');

interface SingleProps {
  code?: string;
  internal?: {
    code: string;
    content: string;
    bg: string;
  };
  lang?: Lang;
  showCopy?: boolean;
}

export type GroupItem = Omit<SingleProps, 'showCopy'> & { title: ReactNode };
export interface GroupProps {
  group: GroupItem[];
  showCopy?: boolean;
}

function isGroupBlock(p: unknown): p is GroupProps {
  return !!(p as any).group;
}

export const CodeBlock = (data: SingleProps | GroupProps) => {
  const { t } = useTranslation();
  const { showCopy = true } = data;
  const [active, setActive] = useState(0);
  const [userCode, setUserCode] = useState('');
  const [codeContent, setCodeContent] = useState('');
  const [bg, setBg] = useState('');
  const highlighter = useRef<Highlighter | null>(null);
  const handleCodeContent = async ({
    code,
    internal,
    lang = 'javascript',
  }: Omit<SingleProps, 'showCopy'>) => {
    if (internal) {
      setBg(internal.bg);
      setUserCode(internal.code);
      setCodeContent(internal.content);
      sh.get({
        lang,
      }).then((h) => {
        highlighter.current = h;
      });
      return;
    }
    if (code) {
      // 代码块里可能会需要 i18n / 不同构建模式的输出不同 / 访问环境变量
      // - 使用i18n：{t('xxx')}
      // - 访问环境变量: {VITE_XXXX}
      // - 注入特殊变量:
      //   - {deployUrl}
      //   - {deployPath}
      //   - {enableSSL}
      const resultCode = (code as string)
        .replace(/\{t\(['"](.*?)['"]\)\}/g, (_, key) => {
          return t(key);
        })
        .replace(/\{VITE_(.*?)\}/g, (_, key) => {
          return import.meta.env[`VITE_${key}`];
        })
        .replace(/\{deployUrl\}/g, deployUrl)
        .replace(/\{deployPath\}/g, deployPath)
        .replace(/\{enableSSL\}/g, enableSSL);

      if (!highlighter.current) {
        highlighter.current = await sh.get({
          lang,
        });
      }
      const bg = highlighter.current.getBackgroundColor();
      const html = highlighter.current.codeToHtml(resultCode, {
        lang,
      });
      setBg(bg);
      setUserCode(resultCode);
      setCodeContent(html);
    }
  };

  useEffect(() => {
    if (isGroupBlock(data)) {
      handleCodeContent(data.group[active]);
    } else {
      handleCodeContent(data);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [active, data]);

  const [copyStatus, setCopyStatus] = useState(false);
  useEffect(() => {
    if (!copyStatus) return;
    const timer = setTimeout(() => {
      setCopyStatus(false);
    }, 1500);
    return () => {
      clearTimeout(timer);
    };
  }, [copyStatus]);

  const onCopy = useCallback(() => {
    if (copyStatus) return;
    const res = copy(userCode);
    setCopyStatus(res);
  }, [copyStatus, userCode]);

  if (!codeContent) return <LoadingFallback />;

  return (
    <div
      style={{ backgroundColor: bg }}
      className="code-block"
      tabIndex={0}
      onKeyDown={(e) => {
        if ((e.metaKey || e.ctrlKey) && e.key === 'a') {
          e.preventDefault();
          const selection = window.getSelection();
          const codeContent = e.currentTarget.querySelector(
            '.code-block-content',
          );
          if (selection && codeContent) {
            const range = document.createRange();
            range.selectNodeContents(codeContent);
            selection.removeAllRanges();
            selection.addRange(range);
          }
        }
      }}
    >
      <div className="code-block-title">
        {isGroupBlock(data) &&
          data.group.map((c, index) => {
            return (
              <div
                key={index}
                className={clsx('title-item', {
                  active: active === index,
                })}
                onClick={() => {
                  setActive(index);
                }}
              >
                {c.title}
              </div>
            );
          })}
      </div>
      <div className="code-block-content">
        <div
          dangerouslySetInnerHTML={{
            __html: codeContent,
          }}
        />
      </div>
      {showCopy && (
        <button className="copy-code" onClick={onCopy}>
          {copyStatus ? (
            <Space>
              <span>Copied</span>
              <Divider type="vertical" style={{ backgroundColor: '#666' }} />
              <Icon component={CopiedSvg} style={{ fontSize: 18 }} />
            </Space>
          ) : (
            <Icon component={CopySvg} style={{ fontSize: 18 }} />
          )}
        </button>
      )}
    </div>
  );
};
